package com.dems.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dems.core.enumerate.SignalTypeEnum;
import com.dems.core.util.TokenUtil;
import com.dems.dao.dataobject.*;
import com.dems.dao.mapper.la.*;
import com.dems.domain.constant.GeWuApiConstant;
import com.dems.domain.device.DevicePropertyValue;
import com.dems.domain.signal.SignalProperty;
import com.dems.service.AsyncDeviceService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

@Service
@Slf4j
public class AsyncDeviceServiceImpl implements AsyncDeviceService {

    private static final String BASE_URL = "https://gwapi.10646.cn/api";

    @Value("${gewu.collect.appId:0OLO8KM5gD}")
    private String appId;

    @Value("${gewu.collect.appSecret:Vf0Cm06zxBgwUZbBIhnAAuVAwhHSdk}")
    private String appSecret;


    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private ResDeviceMapper deviceMapper;

    @Autowired
    private ProductInfoMapper productInfoMapper;

    @Autowired
    private CfgSignalMapper cfgSignalMapper;

    @Autowired
    private DeviceSignalDataMapper deviceSignalDataMapper;

    private CfgAlarmStrategyMapper cfgAlarmStrategyMapper;

    private CfgAlarmMessageMapper cfgAlarmMessageMapper;

    @Override
    public void asyncProductList() {
        String timestamp = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss SSS");
        String transId = generateTransId();
        String token = TokenUtil.generateToken(appId, appSecret, transId, timestamp);
        JSONObject data = new JSONObject();
        data.put("currentPage", 1);
        data.put("pageSize", 100);
        JSONObject param = new JSONObject();
        param.put("app_id", appId);
        param.put("timestamp", timestamp);
        param.put("trans_id", transId);
        param.put("token", token);
        param.put("data", data);
        HttpEntity<?> request = buildHttpEntity(param);
        String responseStr = restTemplate.postForObject(BASE_URL + GeWuApiConstant.LIST_PRODUCTS_URL, request, String.class);
        log.info("async ProductList response:{}", responseStr);
        JSONObject respObj = JSON.parseObject(responseStr);
        if(respObj.getString("code").equals("000000")){
            List<ProductInfo> productInfos = respObj.getJSONObject("data").getJSONArray("list").toJavaList(ProductInfo.class);
            for(ProductInfo productInfo : productInfos){
                productInfo.setCreateTime(new Date());
                productInfoMapper.insertSelective(productInfo);
            }
        }
    }

    @Override
    public void asyncDeviceList() {
        String timestamp = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss SSS");
        String transId = generateTransId();
        String token = TokenUtil.generateToken(appId, appSecret, transId, timestamp);
        List<ProductInfo> productInfos = productInfoMapper.queryProductInfoList();
        for(ProductInfo productInfo : productInfos) {
            JSONObject data = new JSONObject();
            data.put("productKey", productInfo.getProductKey());
            data.put("currentPage", 1);
            data.put("pageSize", 100);
            JSONObject param = new JSONObject();
            param.put("app_id", appId);
            param.put("timestamp", timestamp);
            param.put("trans_id", transId);
            param.put("token", token);
            param.put("data", data);
            HttpEntity<?> request = buildHttpEntity(param);
            String responseStr = restTemplate.postForObject(BASE_URL + GeWuApiConstant.LIST_DEVICES_URL, request, String.class);
            log.info("async DeviceList response:{}", responseStr);
            JSONObject respObj = JSON.parseObject(responseStr);
            if (respObj.getString("code").equals("000000")) {
                List<ResDevice> resDevices = respObj.getJSONObject("data").getJSONArray("list").toJavaList(ResDevice.class);
                for (ResDevice device : resDevices) {
                    device.setCreateTime(new Date());
                    deviceMapper.insertSelective(device);
                }
            }
        }
    }

    @Override
    public void asyncDeviceBaseInfos() {
        String timestamp = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss SSS");
        String transId = generateTransId();
        String token = TokenUtil.generateToken(appId, appSecret, transId, timestamp);
        List<ResDevice> devices = deviceMapper.queryDeviceList(null);
        for(ResDevice device : devices){
            JSONObject data = new JSONObject();
            data.put("productKey", device.getProductKey());
            data.put("deviceKey", device.getDeviceKey());
            JSONObject param = new JSONObject();
            param.put("app_id", appId);
            param.put("timestamp", timestamp);
            param.put("trans_id", transId);
            param.put("token", token);
            param.put("data", data);
            HttpEntity<?> request = buildHttpEntity(param);
            String responseStr = restTemplate.postForObject(BASE_URL + GeWuApiConstant.GET_DEVICE_URL, request, String.class);
            log.info("async DeviceList response:{}", responseStr);
            JSONObject respObj = JSON.parseObject(responseStr);
            if (respObj.getString("code").equals("000000")) {
                ResDevice baseDev = respObj.getJSONObject("data").toJavaObject(ResDevice.class);
                BeanUtils.copyProperties(baseDev, device);
                deviceMapper.updateByPrimaryKeySelective(device);
            }
        }
    }

    @Override
    public void asyncDeviceMonitorData() {
        String timestamp = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss SSS");
        String transId = generateTransId();
        String token = TokenUtil.generateToken(appId, appSecret, transId, timestamp);
        List<ResDevice> devices = deviceMapper.queryDeviceList(null);
        for(ResDevice device : devices){
            JSONObject data = new JSONObject();
            data.put("productKey", device.getProductKey());
            data.put("deviceKey", device.getDeviceKey());
            JSONObject param = new JSONObject();
            param.put("app_id", appId);
            param.put("timestamp", timestamp);
            param.put("trans_id", transId);
            param.put("token", token);
            param.put("data", data);
            HttpEntity<?> request = buildHttpEntity(param);
            String responseStr = restTemplate.postForObject(BASE_URL + GeWuApiConstant.GET_PROPERTY_VALUES_URL, request, String.class);
            log.info("async DeviceList response:{}", responseStr);
            JSONObject respObj = JSON.parseObject(responseStr);
            if (respObj.getString("code").equals("000000")) {
                List<DevicePropertyValue> devProList = respObj.getJSONObject("data").getJSONArray("list").toJavaList(DevicePropertyValue.class);
                for(DevicePropertyValue devProValue : devProList){
                    CfgSignal signal = cfgSignalMapper.selectByCode(devProValue.getKey());
                    if(signal != null){
                        DeviceSignalData deviceSignalData = new DeviceSignalData();
                        deviceSignalData.setDeviceId(device.getId());
                        deviceSignalData.setDeviceName(device.getDeviceName());
                        deviceSignalData.setSignalId(signal.getId());
                        deviceSignalData.setSignalName(signal.getSignalName());
                        deviceSignalData.setCollectTime(new Date(devProValue.getTs()));
                        deviceSignalData.setCollectValue(devProValue.getValue());
                        deviceSignalDataMapper.insertSelective(deviceSignalData);
                    }
                }
            }
        }
    }

    @Override
    public void asyncDeviceModel() {
        String timestamp = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss SSS");
        String transId = generateTransId();
        String token = TokenUtil.generateToken(appId, appSecret, transId, timestamp);
        List<ProductInfo> productInfos = productInfoMapper.queryProductInfoList();
        for(ProductInfo productInfo : productInfos) {
            JSONObject data = new JSONObject();
            data.put("productKey", productInfo.getProductKey());
            JSONObject param = new JSONObject();
            param.put("app_id", appId);
            param.put("timestamp", timestamp);
            param.put("trans_id", transId);
            param.put("token", token);
            param.put("data", data);
            HttpEntity<?> request = buildHttpEntity(param);
            String responseStr = restTemplate.postForObject(BASE_URL + GeWuApiConstant.GET_THING_DEFINITION_URL, request, String.class);
            log.info("async thingDefinition response:{}", responseStr);
            JSONObject respObj = JSON.parseObject(responseStr);
            if (respObj.getString("code").equals("000000")) {
                JSONObject dataObject = respObj.getJSONObject("data").getJSONObject("thingDefinitonJson");
                List<SignalProperty> signalProperties = dataObject.getJSONArray("properties").toJavaList(SignalProperty.class);
                signalProperties.forEach(signalProperty -> {
                    CfgSignal signal = new CfgSignal();
                    signal.setSignalCode(signalProperty.getKey());
                    signal.setSignalName(signalProperty.getName());
                    String type = signalProperty.getDataType().getString("type");
                    if("float".equals(type) && "r".equals(signalProperty.getAccessMethod())){
                        signal.setSignalType(SignalTypeEnum.AI.getCode());
                    }
                    if("float".equals(type) && "rw".equals(signalProperty.getAccessMethod())){
                        signal.setSignalType(SignalTypeEnum.AO.getCode());
                    }
                    if("enum".equals(type) && "r".equals(signalProperty.getAccessMethod())){
                        signal.setSignalType(SignalTypeEnum.DI.getCode());
                    }
                    if("float".equals(type) && "rw".equals(signalProperty.getAccessMethod())){
                        signal.setSignalType(SignalTypeEnum.DO.getCode());
                    }
                    signal.setMetadata(signalProperty.getDataType().toJSONString());
                    signal.setProductKey(productInfo.getProductKey());
                    signal.setProductName(productInfo.getProductName());
                    cfgSignalMapper.insertSelective(signal);
                });
            }
        }
    }

    public HttpEntity<Object> buildHttpEntity(JSONObject param) {
        HttpHeaders requestHeaders = new HttpHeaders();
        // Authorization: `Bearer:${token}`
        requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        requestHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new HttpEntity<>(param, requestHeaders);
    }

    public String generateTransId(){
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        int randomNumber = ThreadLocalRandom.current().nextInt(100000, 999999);
        String transId = format.format(new Date())+randomNumber;
        return transId;
    }

    /**
     * 判断告警信息，根据自定义告警规则判断设备采集数据是否满足告警条件
     * */
    public void criteriaAlarm(DeviceSignalData deviceSignalData){
        Page page = new Page(1, 100);
        CfgAlarmStrategy cfgAlarmStrategy = new CfgAlarmStrategy();
        cfgAlarmStrategy.setDeviceId(deviceSignalData.getDeviceId());
        cfgAlarmStrategy.setSignalId(deviceSignalData.getSignalId());
        cfgAlarmStrategy.setAlarmType("1");
        Page<CfgAlarmStrategy> pageInfo = cfgAlarmStrategyMapper.queryByPage(page, cfgAlarmStrategy);
        List<CfgAlarmStrategy> alarmStrategyList = pageInfo.getRecords();
        if(CollectionUtils.isNotEmpty(alarmStrategyList)){
            String devSig = deviceSignalData.getDeviceId() + "." + deviceSignalData.getSignalId();
            for(CfgAlarmStrategy alarmStrategy : alarmStrategyList){
                String express = alarmStrategy.getAlarmCondition().replace(devSig, deviceSignalData.getCollectValue().toString());
                CfgAlarmMessage cfgAlarmMessage = new CfgAlarmMessage();
                cfgAlarmMessage.setDeviceId(deviceSignalData.getDeviceId());
                cfgAlarmMessage.setSignalId(deviceSignalData.getSignalId());
                cfgAlarmMessage.setAlarmLevel(alarmStrategy.getAlarmLevel());
                CfgAlarmMessage alarmMessage = cfgAlarmMessageMapper.selectLastAlarm(cfgAlarmMessage);
                if(executeExpress(express)){//为true产生告警
                    //判断是否有未结束的告警，存在未结束告警且告警发生时间在延时处理时间内则不重复输出告警
                    if(alarmMessage != null && (System.currentTimeMillis() - alarmMessage.getStartTime().getTime())< alarmStrategy.getDelayTime() * 60 * 1000L){
                        return;
                    }else {
                        //产生新的告警记录
                        cfgAlarmMessage.setAlarmType(cfgAlarmStrategy.getAlarmType());
                        cfgAlarmMessage.setDeviceName(deviceSignalData.getDeviceName());
                        cfgAlarmMessage.setSignalName(deviceSignalData.getSignalName());
                        cfgAlarmMessage.setStartTime(new Date());
                        cfgAlarmMessage.setCreateTime(new Date());
                        cfgAlarmMessage.setContent(cfgAlarmStrategy.getAlarmContent());
                        cfgAlarmMessageMapper.insertSelective(cfgAlarmMessage);
                    }
                }
                else if(alarmMessage != null){//判断是否满足告警恢复条件，判断采集值与告警解除阈值之间的关系
                    alarmMessage.setEndTime(new Date());
                    cfgAlarmMessageMapper.updateByPrimaryKeySelective(alarmMessage);
                }
            }
        }
    }

    public boolean executeExpress(String express){
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine se = manager.getEngineByName("js");
        boolean result = false;
        try {
            result = (boolean) se.eval(express);
        } catch (ScriptException e) {
            throw new RuntimeException(e);
        }
        return result;
    }
}
